3.5 - 用户管理vxzvcfzvdfzsgbvd
在本次用户管理任务中,我们需要连接数据库实现用户的增删改查。我们可以通过用户新建页面提交新建用户并记录到数据库,通过用户列表页面获取数据库中用户的数据并展示出来,在用户编辑页中对匹配的用户内容进行编辑和修改。在本次任务中,我们推荐用 knex.js 完成数据库的交互逻辑,同时我们对这部分的数据库及逻辑操作有 MVC 代码结构的要求。
Knex.js是为 Postgres,MSSQL,MySQL,MariaDB,SQLite3,Oracle 和 Amazon Redshift设计的SQL查询构建器,其设计灵活,便于携带并且使用起来非常有趣。它具有传统的节点样式回调以及用于清洁异步流控制的承诺接口,流接口,全功能查询和模式构建器,事务支持(带保存点),连接池 以及不同查询客户和方言之间的标准化响应。Knex的主要目标环境是Node.js,您需要安装该 knex 库,然后安装适当的数据库库。
任务要求:
安装 Knex.js 连接数据库。
1 | npm install -save knex mysql |
knex中文文档 https://www.songxingguo.com/2018/06/30/knex.js-query/
在根目录中新建 config 文件用来存放数据库的配置信息。
1 | const configs = { |
在根目录中新建 models 文件夹用户存放数据库操作相关文件。
models/knex.js 数据库配置,用于初始化mysql1
2
3
4
5
6
7
8
9
10
11const configs = require('../config');//引入初始化配置信息
module.exports = require('knex')({
client: 'mysql',
connection: {
host: configs.mysql.host,//把configs里的配置信息赋值到host,下同
port: configs.mysql.port,
user: configs.mysql.user,
password: configs.mysql.password,
database: configs.mysql.database,
}
})
新建 models/base.js 基础操作模型(模块化),很多数据库的操作例如简单的增删改查都可以通过继承方法来使用,封装暴露我们自定义的方法,也方便后期 knex 启用的切换不会影响到其他 models 和 controllers 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const knex = require('./knex');
class Base {
constructor(props) {
this.table = props;
}
all(){//全部
return knex(this.table).select()
}
select(params){//挑选、查找
return knex(this.table).select().where(params)
}
insert(params){//插入
return knex(this.table).insert(params)
}
update(id,params){//修改
return knex(this.table).where('id','=',id).update(params)
}
delete(id){//删除
return knex(this.table).where('id','=',id).del()
}
}
module.exports = Base
新建 models/user.js 用户模型, 继承 base1
2
3
4
5
6
7const Base = require('./base.js');
class User extends Base {
constructor(props = 'user') {
super(props);
}
}
module.exports = new User()
由于数据库存储的日期的数据类型为 Date 类型,因此我们需要新增一个工具函数来进行数据处理,然后在 controllers 中引入进行数据重组。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
module.exports = {
formatTime: formatTime
}
在根目录中新建 controllers 文件夹新建页面逻辑相关的文件。
1 | const User = require('./../models/user.js');//引入数据库模块 |
在根目录中 routes 文件夹中,新建 api.js 文件用户配置 API 相关路由。
1 | var express = require('express'); |
app.js引用api.js文件1
2
3var apiRouter = require('./routes/api');
···
app.use('/api', apiRouter);
在页面路由中,修改用户列表、用户新增、用户修改页面的路由,引用 controller 中的对应的方法。1
2
3
4
router.get('/admin/user', userController.show);
router.get('/admin/user/create', userController.renderUserCreate);
router.get('/admin/user/:id/edit', userController.edit);
在用户新增页面,输入用户姓名、电话、密码、角色提交后数据能存进数据库,成功返回到用户列表页面。
views/admin/user_create.tpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35{% extends './../admin_layout.tpl' %}
{% block content %}
<div class="content-title">新增人员</div>
<div class="content-control">
<a href="/admin/user">返回用户列表 >></a>
</div>
<div class="form-section">
<div class="form-item">
<input id="userName" type="text" class="form-input" placeholder="姓名"/>
</div>
<div class="form-item">
<input id="userPhone" type="text" class="form-input" placeholder="电话"/>
</div>
<div class="form-item">
<input id="userPassword" type="text" class="form-input" placeholder="密码"/>
</div>
<div class="form-item">
<select class="form-input" id="userRole">
<option value="0">请选择角色</option>
<option value="1">管理员</option>
<option value="2">销售</option>
</select>
</div>
<div class="form-item">
<button id="userSubmit" class="form-button">新增</button>
</div>
</div>
{% endblock %}
{% block js %}
<script src="/javascripts/jquery-3.3.1.min.js"></script>//可以自行下载保存也可以使用云端
<script src="/javascripts/user_create.js"></script>
{% endblock %}
public/javascripts/user_create.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43const PAGE = {
init: function() {
this.bind();
},
bind: function() {
$('#userSubmit').bind('click',this.handleSubmit);
},
handleSubmit: function() {
let name = $('#userName').val();
let phone = $('#userPhone').val();
let password = $('#userPassword').val();
let role = $('#userRole').val();
role = Number(role)
if(!name || !phone || !password || !role){
alert('请输入必要参数啊大锅');
return
}
$.ajax({
url: '/api/user',
data: { name, phone, password, role },
type: 'POST',
beforeSend: function() {
$("#userSubmit").attr("disabled",true);
},
success: function(data) {
if(data.code === 200){
alert('新增成功!')
location.href = '/admin/user'
}else{
alert(data.message)
}
},
error: function(err) {
console.log(err)
},
complete: function() {
$("#userSubmit").attr("disabled",false);
}
})
}
}
PAGE.init();
在用户列表页面,展示数据库中的所有用户信息,并生产对应的编辑地址。
views/admin/user.tpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36{% extends './../admin_layout.tpl' %}
{% block content %}
<div class="content-title">人员管理</div>
<div class="content-control">
<a href="/admin/user/create">新增人员 >></a>
</div>
<div class="content-table">
<table class="table-container">
<tr>
<th>姓名</th>
<th>电话</th>
<th>角色</th>
<th>创建时间</th>
<th>操作</th>
</tr>
{% for val in users %}
<tr>
<td>{{val.name}}</td>
<td>{{val.phone}}</td>
<td>{{ val.role_display}}</td>
<td>{{ val.created_time_display}}</td>
<td>
<a href="/admin/user/{{val.id}}/edit">编辑</a>
<a class="user-del" href="javascript:;" data-id="{{val.id}}">删除</a>
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}
{% block js %}
<script src="/javascripts/jquery-3.3.1.min.js"></script>
<script src="/javascripts/user_page.js"></script>
{% endblock %}
public/javascripts/user_page.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//这里只有删除功能
const PAGE = {
init:function(){
this.bind();
},
bind:function(){
$('.user-del').on('click',this.userDel)
},
userDel: function() {
let id = $(this).data('id');
$.ajax({
url: '/api/user',
data: { id },
type: 'DELETE',
success: function(data) {
if(data.code === 200){
alert('删除成功!')
location.reload()
}else{
console.log(data)
}
},
error: function(err) {
console.log(err)
}
})
},
}
PAGE.init();

在用户编辑页面,可以修改对应的用户相关信息,成功返回到用户列表页面。
views/admin/user_edit.tpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36{% extends './../admin_layout.tpl' %}
{% block content %}
<div class="content-title">编辑人员</div>
<div class="content-control">
<a href="/admin/user">返回用户列表 >></a>
</div>
<div class="form-section">
<div class="form-item">
<input id="userName" type="text" class="form-input" placeholder="姓名" value="{{user.name}}"/>
</div>
<div class="form-item">
<input id="userPhone" type="text" class="form-input" placeholder="电话" value="{{user.phone}}"/>
</div>
<div class="form-item">
<input id="userPassword" type="text" class="form-input" placeholder="密码" value="{{user.password}}"/>
</div>
<div class="form-item">
<select id="userRole" class="form-input">
<option value="0">请选择角色</option>
<option value="1" {% if user.role == 1 %} selected {% endif %}>管理员</option>
<option value="2" {% if user.role == 2 %} selected {% endif %}>销售</option>
</select>
</div>
<div class="form-item">
<input id="userId" type="text" hidden value="{{user.id}}" />
<button id="userSubmit" class="form-button">保存</button>
</div>
</div>
{% endblock %}
{% block js %}
<script src="/javascripts/jquery-3.3.1.min.js"></script>
<script src="/javascripts/user_edit.js"></script>
{% endblock %}
public/javascripts/user_edit.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45const PAGE = {
init: function() {
this.bind();
},
bind: function() {
$('#userSubmit').bind('click',this.handleSubmit);
},
handleSubmit: function() {
let id = $('#userId').val();
let name = $('#userName').val();
let phone = $('#userPhone').val();
let password = $('#userPassword').val();
let role = $('#userRole').val();
role = Number(role)
if(!name || !phone || !password || !role){
alert('请输入必要参数');
return
}
$.ajax({
url: '/api/user/' + id,
data: { name, phone, password, role },
type: 'PUT',
beforeSend: function() {
$("#userSubmit").attr("disabled",true);
},
success: function(data) {
if(data.code === 200){
alert('编辑成功!')
location.href = '/admin/user'
}else{
alert(data.message)
}
},
error: function(err) {
console.log(err)
},
complete: function() {
$("#userSubmit").attr("disabled",false);
}
})
}
}
PAGE.init();
登录与退出
在上节任务中,我们实现了用户数据的增删改查,我们用户的数据表中已经拥有了相关的用户。在这次任务中,我们需要在登录页面输入对应的用户手机、密码,如果和数据库中数据匹配校验成功给予登录,否则登录失败。在登录成功后,我们需要通过用户相关的数据加密生成 token 存放在 cookie 中,当用户下一次来的时候如果发现这个 cookie 那么保持用户等登录状态,当用户点击退出的时候,清除对应的 cookie 那么返回到最初始未登录的状态。
登陆页输入手机和密码进行登录。
服务器需要保持用户登录的状态,在下次到登陆页的时候重定向到线索页面。
服务器需要判断用户登录的状态,在未登录情况下去后台相关页面重定向到登录页。
在后台公共头部添加退出按钮,点击退出并重定向到登录页面。
1、在用户登录的 controller 方法中,获取用户提交的电话、密码,到数据库中查询是否有这个用户。如果没有,返回账户密码错误,如果有继续下一步逻辑。
2、把用户的账户、密码、ID 加密成 token ,连同用户名存放在 cookie 中。
controllers/auth.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42const User = require('./../models/user.js');
const authCodeFunc = require('./../utils/authCode.js');
const authController = {
login:async function(req,res,next){
// 获取邮件密码参数
let phone = req.body.phone;
let password = req.body.password;
// 参数判断
if(!phone || !password){
res.json({ code: 0, data: 'params empty!' });
return
}
try{
// 通过用户模型搜索用户
const users = await User.select({ phone, password });
// 看是否有用户存在
const user = users[0];
// 如果存在
if(user){
// 将其邮箱、密码、id 组合加密
let auth_Code = phone +'\t'+ password +'\t'+ user.id +'\t'+ user.role;
auth_Code = authCodeFunc(auth_Code,'ENCODE');
// 加密防止再 cookie 中,并不让浏览器修改
res.cookie('ac', auth_Code, { maxAge: 24* 60 * 60 * 1000, httpOnly: true });
// 返回登录的信息
res.json({ code: 200, message: '登录成功!'})
}else{
res.json({ code: 0, message: '登录失败,没有此用户!' })
}
}catch(e){
res.json({ code: 0, message: '系统问题请管理员处理' })
}
},
// 渲染登录页面的模版
renderLogin:async function(req,res,next){
res.render('admin/login')
}
}
module.exports = authController;
admin/login.tpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31{% extends './../layout.tpl' %}
{% block css %}
<link rel="stylesheet" href="/stylesheets/login.css"/>
{% endblock %}
{% block content %}
<div class="wrapper">
<div class="form-section">
<!-- <div class="form-title">管理系统后台登录</div> -->
<img class="form-title" src="https://www.mercedes-benz.com.cn/content/dam/mb-cn/footer/mercedes-benz-logo-desktop.png">
<div class="form-item">
<input id="userPhone" type="text" class="form-input" placeholder="你的手机"/>
</div>
<div class="form-item">
<input id="userPassword" type="text" class="form-input" placeholder="你的电话"/>
</div>
<div class="form-item">
<button id="userSubmit" class="form-button">内部人员登录</button>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="/javascripts/jquery-3.3.1.min.js"></script>
<script src="/javascripts/login.js"></script>
{% endblock %}
login.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36const PAGE = {
init:function(){
this.bind();
},
bind:function(){
$('#userSubmit').on('click',this.handleSubmit)
},
handleSubmit:function(){
let phone = $('#userPhone').val();
let password = $('#userPassword').val();
$.ajax({
url:'/api/login',
data:{phone,password},
type:'POST',
beforeSend: function(){
$('#userSubmit').attr('disabled',true);
},
success: function(data){
if(data.code === 200){
alert('登陆成功啦');
location.href = '/admin/user'
}else{
alert(data.message)
}
},
error: function(err){
console.log(err)
},
complete: function(){
$('#userSubmit').attr('disabled',false);
},
})
},
}
PAGE.init();

新建一个 middleware的中间层配置,用户判断是否登录,并在路由中引入使用。如果该路由中间件判断用户未登录,即可重定向到登录页面。
filters/initFilter1
2
3
4
5
6
7
8module.exports = function(req,res,next) {
res.locals.seo = {
title:'MercedesBenz CRM',
keywords:'crm',
description:'mercedesbenz-crm'
}
next();
}
filter/loginFilter1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const authCodeFunc = require('./../utils/authCode.js');
module.exports = function(req,res,next){
res.locals.isLogin = false;
res.locals.userInfo = {};
let auth_Code = req.cookies.ac;
if(auth_Code){
auth_Code = authCodeFunc(auth_Code,'DECODE');
authArr = auth_Code.split("\t");
let phone = authArr[0];
let password = authArr[1];
let id = authArr[2];
let role = authArr[3];
res.locals.isLogin = true;
res.locals.userInfo = {
phone,password,id,role
}
}
next();
}
filter/index1
2
3
4module.exports = function(app) {
app.use(require('./initFilter.js'));
app.use(require('./loginFilter.js'))
}
新建中间层
middlewares/suth.js1
2
3
4
5
6
7
8
9
10
11
12
13const authMiddleware = {
mustLogin: function(req,res,next){
if(!res.locals.isLogin){
res.redirect('/admin/login')
return
}
next();
},
}
module.exports = authMiddleware;
在渲染登录页面的 controller 逻辑中,可以获取之前存放在 res.locals 中的数据判断用户是否登录,如果登录了,就重定向到线索管理页面。
1 | // 渲染登录页面的模版 |
router/index
1 | var express = require('express'); |
设置跳转模块,网页要清除cookie才能顺利退出
controllers/outlogin.js1
2
3
4
5
6
7
8const logoutcontroller = {
outlogin:async function(req,res,next){
res.clearCookie('ac','user_name');//清除cookie
res.clearCookie('user_name');
res.redirect('/admin/login');//设置跳转路径
}
}
module.exports = logoutcontroller;
routes/index1
2
3
4
5
6
7···
var logoutcontroller = require('./../controllers/outlogin.js');//引入跳转模块
/* GET home page. */
···
router.get('/admin/outlogin',logoutcontroller.outlogin);//设置路由
module.exports = router;
在公共后台加上退出选项
1 | <div class="wrapper"> |